package world.snowcrystal.nova.scoring;

import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import world.snowcrystal.nova.ai.AITemplate;
import world.snowcrystal.nova.ai.command.QuestionContentCommand;
import world.snowcrystal.nova.common.ErrorCode;
import world.snowcrystal.nova.exception.BusinessException;
import world.snowcrystal.nova.model.dto.question.QuestionDto;
import world.snowcrystal.nova.model.entity.App;
import world.snowcrystal.nova.model.entity.Question;
import world.snowcrystal.nova.model.entity.UserAnswer;
import world.snowcrystal.nova.model.enums.AppScoringStrategyEnum;
import world.snowcrystal.nova.model.enums.AppScoringTypeEnum;
import world.snowcrystal.nova.model.vo.QuestionVO;
import world.snowcrystal.nova.service.QuestionService;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
public class AiAssessmentScoringStrategy implements ScoringStrategy {

    private static final String AI_ANSWER_CACHE_KEY = "ai_answer_cache_key";

    private static final String SYSTEM_MESSAGE = """
            你是一位严谨的判题专家，我会给你如下信息：
            ```
            应用名称，
            【【【应用描述】】】，
            题目和用户回答的列表：格式为 [{"title": "题目","answer": "用户回答"}]
            ```

            请你根据上述信息，按照以下步骤来对用户进行评价：
            1. 要求：需要给出一个明确的评价结果，包括评价名称（尽量简短）和评价描述（尽量详细，大于 200 字）
            2. 严格按照下面的 json 格式输出评价名称和评价描述
            ```
            {"resultName": "评价名称", "resultDesc": "评价描述"}
            ```
            3. 返回格式必须为 JSON 对象""";

    private static final String USER_MESSAGE_TEMPLATE = """
                %s,
                【【【%s】】】,
                %s
            """;

    @Resource
    private QuestionService questionService;

    @Resource
    private AITemplate aiTemplate;

    @Resource
    private RedissonClient redissonClient;

    private final Cache<String, String> answerCacheMap = Caffeine.newBuilder()
            .initialCapacity(1024).expireAfterAccess(5L, TimeUnit.MINUTES)
            .build();

    /**
     * 执行评分
     */
    @Override
    public UserAnswer score(List<String> choices, App app) throws Exception {

        Long appId = app.getId();
        String choicesJson = JSONUtil.toJsonStr(choices);
        String key = buildCacheKey(appId, choicesJson);
        String userAnswerJson = answerCacheMap.getIfPresent(key);

        if (StringUtils.hasText(userAnswerJson)) {
            UserAnswer bean = JSONUtil.toBean(userAnswerJson, UserAnswer.class);
            bean.setAppId(appId)
                    .setAppType(app.getAppType())
                    .setScoringStrategy(app.getScoringStrategy()).setChoices(JSONUtil.toJsonStr(choices));
            return bean;
        }
        // no cache hit
        RLock lock = redissonClient.getLock(AI_ANSWER_CACHE_KEY);
        try {
            if (!lock.tryLock(3L, 15L, TimeUnit.SECONDS)) {
                return null;
            }
            final Question question = questionService.getOne(
                    Wrappers.lambdaQuery(Question.class)
                            .eq(Question::getAppId, appId)
            );
            QuestionVO questionVO = QuestionVO.objToVo(question);
            List<QuestionDto> questionContent = questionVO.getQuestionContent();

            String userMessage = getAiTestScoringUserMessage(app, questionContent, choices);
            // 调用 AI 获取结果

            String aiResponse = aiTemplate.chat(SYSTEM_MESSAGE, userMessage, false, 0.75f);
            int start = aiResponse.indexOf("{");
            int end = aiResponse.lastIndexOf("}");
            String json = aiResponse.substring(start, end + 1);
            answerCacheMap.put(key, json);
            UserAnswer userAnswer = JSONUtil.toBean(json, UserAnswer.class);
            userAnswer.setAppId(appId).setAppType(app.getAppType()).setScoringStrategy(app.getScoringStrategy()).setChoices(JSONUtil.toJsonStr(choices));
            return userAnswer;
        } finally {
            if (lock != null && lock.isLocked()) {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 同时比较 App 的评分策略、应用类型是否符合
     *
     * @param app
     * @return
     */
    @Override
    public boolean isSupport(App app) {
        return app != null
                && app.getScoringStrategy() != null
                && app.getAppType() != null
                && app.getAppType().equals(AppScoringTypeEnum.ASSESSMENT.getValue())
                && app.getScoringStrategy().equals(AppScoringStrategyEnum.AI.getValue());
    }

    private String getAiTestScoringUserMessage(App app, List<QuestionDto> questionDtos, List<String> choices) {
        if (questionDtos.size() != choices.size()) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "还没有答完哦");
        }
        List<QuestionContentCommand> questionAnswerDTOList = new ArrayList<>();
        for (int i = 0; i < questionDtos.size(); i++) {
            QuestionContentCommand questionAnswerDTO = new QuestionContentCommand();
            questionAnswerDTO.setTitle(questionDtos.get(i).getTitle());
            questionAnswerDTO.setUserAnswer(choices.get(i));
            questionAnswerDTOList.add(questionAnswerDTO);
        }
        return String.format(USER_MESSAGE_TEMPLATE, app.getAppName(), app.getAppDesc(), JSONUtil.toJsonStr(questionAnswerDTOList));
    }


    private String buildCacheKey(Long appId, String choices) {
        return DigestUtil.md5Hex(String.format("%s-%s", appId, choices));
    }
}
